/******************************************************************************
 *  _____     _____
 *  \  __\    \  __\
 *   \ \_\\    \ \_\\
 *    \  __\SS  \  __\roject
 *     \_\       \_\
 *
 * Mateusz Maciąg, Rafał Urbański
 * Komputerowe Systemy Sterowania
 *****************************************************************************/

#ifndef CONTROLLERBASE_H
#define CONTROLLERBASE_H

#include "SISOModel.h"
#include "WaveformContainer.h"

// Przestrzeń nazw
namespace RafMat
{
namespace SimEngine
{

// Deklaracja zapowiadająca
class ControllerBase;

/** Alias na zręczny wskaźnik dla klasy ControllerBase */
typedef boost::shared_ptr<ControllerBase> ControllerBaseSPtr;

/**
    Klasa bazowa regulatora.

    Klasa reprezentuje ogólny interfejs regulatora. Poszczególne rodzaje
    regulatorów powinny dziedziczyć po tej klasie (\see GainController, OpenLoopController,
    PIDController, etc.).

    Regulator jest rodzajem obiektu wejściowo-wyjściowego (SISOModel) i implementuje
    metode simulateStep realizującą dynamikę tego regulatora.
    Metoda ta domyślnie ewaluuje wartość zadaną, oblicza uchyb regulacji oraz wywołuje
    chronioną metodę simulateControllerStep, która powinna być implementowana
    przez klasy potomne (klasy potomne nie muszą reimplementować już simulateStep).

    Wartości uchybu, aktualnej wartości zadanej oraz aktualnego wyjścia regulatora
    są dostępne przez odpowiednie metody dostępowe currentSetPoint, currentError,
    currentOutput. Wielkości te są przechowywane w prywatnych polach klasy i obliczane
    tylko raz na krok symulacji, przez co dostęp do nich jest szybki.

    Założeniem regulatora jest posiadanie generatora wartości zadanych. Jest ono
    realizowane za pomocą instancji obiektu WaveformContainer. Instancja ta
    może być swobodnie zmieniana z zewnątrz (dzięki metodom get i set).

    \todo Jest wałek przy rozpoznawaniu typu regulatora przez obiekty konfiguracyjne
    IConfigurable. Kontroler powinien przechowywać stałą wartość typu
    ISimulator::ControllerType jednoznacznie identyfikującą typ regulatora.
    To samo zresztą dotyczy obiektów klasy WaveformBase i potomnych.
*/
class ControllerBase : public SISOModel
{
public:

    /**
        Konstruktor tworzący bazową wersję regulatora.

        Konstruktor przede wszystkim binduje obiekt (jako obiekt SISO) z
        obiektem symulataora. Ponadto tworzy dynamicznie domyślny kontener
        na sygnały zadane.

        \param [in] isim Referencja do instancji symulatora.
    */
    explicit ControllerBase(SimInterface::ISimulator &isim, std::string type);

    /**
        Konstruktor tworzący bazową wersję regulatora z własnym zestawem wartości zadanych.

        Konstruktor binduje obiekt z obiektem symulatora oraz przypisuje zewnętrzny kontener
        wartości zadanych.

        \param [in] isim Referencja do instancji symulatora.
        \param [in] wfrmContainer Zręczny wskaźnik do kontenera z sygnałami.

        \see WaveformContainer
    */
    ControllerBase(SimInterface::ISimulator &isim, std::string type, WaveformContainerSPtr wfrmContainer);

    /**
        Dodatkowy konstruktor wymuszający nadanie nazwy obiektowi.
    */
    ControllerBase(SimInterface::ISimulator &isim, std::string type, std::string name);

    /**
        Dodatkowy konstruktor wymuszający nadanie nazwy obiektowi.
    */
    ControllerBase(SimInterface::ISimulator &isim, std::string type, std::string name, WaveformContainerSPtr wfrmContainer);

    /**
        Realizuje pracę krokową obiektu regulatora.

        Implementacja metody z klasy bazowej SISOModel. Oblicza aktualne wejście
        na podstawie kontenera z sygnałami wejściowymi (WaveformContainer).
        Wartość jest cache'owana, więc w danym kroku można uzyskać do niej dostęp
        bez kolejnego (złożonego!) przeliczania wartości zadanej.

        Dodatkowo uaktualniana jest wartość uchybu regulacji, do swobodnego wykorzystania
        w klasach potomnych (nieobligatoryjne).
        Zadanie obliczania wartości wyjścia regulatora przekazywane jest funkcji chronionej
        simulateControllerStep, a jej wynik jest jednocześnie zapisywany do zmiennej
        podręcznej i zwracany jako wynik funkcji simulateStep.

        Należy zwrócić uwagę na interpretację próbki wejściowej. Jest to wartość, która
        wchodzi do regulatora od strony sprzężenia zwrotnego układu regulacji.
        Wejście zadawania jest "ukryte" wewnątrz modelu regulatora, tak aby regulator
        mógł być traktowany jako obiekt SISO.

        \param [in] sample Próbka wejścia regulatora.
        \return Wyjście obiektu.
    */
    double simulateStep(double sample);

    /**
        Zwraca aktualną wartość zadawaną.

        \return Wartość zadana.
    */
    double currentSetPoint() const { return m_currentSetPoint; }

    /**
        Zwraca aktualny uchyb regulacji.

        \return Uchyb regulacji.
    */
    double currentError() const { return m_currentError; }

    /**
        Zwraca aktualną wartość wyjścia regulatora.

        Wartość ta jest ostatnią wartością zwróconą przez simulateStep.
        \return Wyjście regulatora.
    */
    double currentOutput() const { return m_currentOutput;}

    /**
        Zwraca aktualną wartość wyjścia sterowanego obiektu.

        Wartość ta jest ostatnią wartością zwróconą przez symulowany obiekt.
        \return Wyjście symulowanego obiektu.
    */
    double currentProcessVariable() const { return m_currentProcessVariable;}

    /**
        Udostępnia kontener na przebiegi wejść zadanych.

        \return Zręczny wskaźnik na kontener.
    */
    WaveformContainerSPtr getWaveformContainer() { return m_waveformContainer; }

    /**
        Zwraca wszystkie wejścia.
    */
    const std::vector<WaveformBaseSPtr>& getWaveforms() { return m_waveformContainer->getWaveforms(); }

    /**
        Ustawia zewnętrzny kontener z wartościami zadanymi.

        \param [in] wcptr Zręczny wskaźnik na kontener z przebiegami użytkownika.
    */
    void setWaveformContainer(WaveformContainerSPtr wcptr) { m_waveformContainer = wcptr; }

    /**
        Wspierane parametry:
        - type - typ regulatora

        \param [in] param Parametr, który chcemy odczytać.
        \return Łańcuch reprezentujący wartość parametru.
        \throws std::logic_error, gdy wykryty zostanie błąd (argumentu lub strumienia danych).
    */
    virtual std::string getParameter(const std::string& param) const throw(std::logic_error);

    /**
        Wysyła stan obiektu do strumienia.

        \param [in/out] o strumień docelowy.
        \return Strumień przekazany funkcji.
    */
    virtual SimFile::ConfigWriter& dumpParameters(SimFile::ConfigWriter& o) const;

protected:

    /**
        Wykonuje krok symulacji regulatora i zwraca nowe wyjście.

        Metoda jest czysto wirtualna, implementacją zajmują się konkretne regulatory
        dziedziczące po tej klasie.

        \param [in] setPoint Wartość zadana.
        \param [in] processVar Zmienna procesowa (pochodząca np. ze sprzężenia zwrotnego).
        \return Nowa wartość wyjścia regulatora.
    */
    virtual double simulateControllerStep(double setPoint, double processVar) = 0;

    /**
        Wrzuca do konfiguracji parametry specyficzne dla danego obiektu.
    */
    virtual SimFile::ConfigWriter& dumpSpecificPar(SimFile::ConfigWriter& o) const;

private:
    static const std::string CONF_TYPE;

    WaveformContainerSPtr m_waveformContainer;          ///< Kontener z przebiegami czasowymi.

    // Cache'owane wartości (w celach optymalizacji).
    double m_currentSetPoint;       ///< Aktualna wartość zadana.
    double m_currentError;          ///< Aktualny uchyb regulacji.
    double m_currentOutput;         ///< Aktualne wyjście regulatora.
    double m_currentProcessVariable;///< Aktualna wyjście obiektu.

    std::string m_type;             ///< Typ regulatora.
};

}   // SimEngine namespace
}   // RafMat namespace


#endif // CONTROLLERBASE_H
